#include <Inventor/Qt/SoQt.h>
#include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
#include <Inventor/actions/SoWriteAction.h>
#include <Inventor/nodes/SoEventCallback.h>
#include <Inventor/events/SoKeyboardEvent.h>
#include <Inventor/events/SoMouseButtonEvent.h>
#include <Inventor/draggers/SoDragPointDragger.h>
#include <Inventor/draggers/SoTranslate2Dragger.h>
#include <Inventor/draggers/SoTranslate1Dragger.h>
#include <Inventor/SoOffscreenRenderer.h>



#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoMaterialBinding.h>
#include <Inventor/nodes/SoIndexedFaceSet.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <list>
#include <vector>

#include "halfedges.h"
SoDragPointDragger *pd = NULL;
SoTranslate2Dragger *td = NULL;
SoTranslate1Dragger *t1d = NULL;
SbVec3f scaleFactor;
Mesh he;
SoGroup *mutants  = NULL;
SoIndexedFaceSet *shape = NULL;
SoCoordinate3 *coords   = NULL;
int niterations = 7;

// тут внимание, коэффициенты упакованы для обхода граней по кругу, в то время как в
// моих матрицах для обычной грани a b d c
vector<vector<float> > coeffs;



void translatePD() {
	if (!pd||!td||!t1d) return;
	pd->translation.setValue(coeffs[1][0]/scaleFactor[0], coeffs[1][1]/scaleFactor[1], coeffs[1][2]/scaleFactor[2]);
	td->translation.setValue(coeffs[7][0]/scaleFactor[0], coeffs[7][1]/scaleFactor[1], 0);
	t1d->translation.setValue(coeffs[2][0]/scaleFactor[0], 0, 0);
}

const float phi = (1+sqrt(5))/2.0;

void init() {
	coeffs.clear();
	niterations = 10;

	coeffs.push_back(vector<float>(2,-1)); //star
	coeffs.push_back(vector<float>(3,-1)); //ace
	coeffs.push_back(vector<float>(1,-1)); //sun
	coeffs.push_back(vector<float>(5,-1)); //king
	coeffs.push_back(vector<float>(3,-1)); //jack
	coeffs.push_back(vector<float>(3,-1)); //queen
	coeffs.push_back(vector<float>(4,-1)); //deuce
	coeffs.push_back(vector<float>(2,-1)); //centers
	coeffs.push_back(vector<float>(1,-1)); //centers
	coeffs.push_back(vector<float>(3,-1)); //d

	coeffs[8][0] = 0.55; //extraordinary

	coeffs[7][0] = -.01;
	coeffs[7][1] = .3;

	//star
	coeffs[0][0] = 0.020001;
	coeffs[0][1] = 0.07;

	//ace
	coeffs[1][0] = .18;
	coeffs[1][1] = .52;
	coeffs[1][2] = .08;

	//sun
	coeffs[2][0] = .91;

	//king
	coeffs[3][0] = .045;//.295;
	coeffs[3][1] = .045;//.045;
	coeffs[3][2] = .045;//-.155;
	coeffs[3][3] = .045;//.245;
	coeffs[3][4] = .045;//-.005;

	//deuce
	coeffs[6][0] = -.075;//-.025;
	coeffs[6][1] = -.355;//-.275;
	coeffs[6][2] = .225;// .185;
	coeffs[6][3] = .225;// .185;

	//D
	coeffs[9][0] = .03;
	coeffs[9][1] = -.08;
	coeffs[9][2] = .43;
	translatePD();
}

static char * buffer;
static size_t buffer_size = 0;
static void * buffer_realloc(void * bufptr, size_t size) {
	buffer = (char *)realloc(bufptr, size);
	buffer_size = size;
	return buffer;
}
static SbString buffer_writeaction(SoNode * root) {
	SoOutput out;
	buffer = (char *)malloc(1024);
	buffer_size = 1024;
	out.setBuffer(buffer, buffer_size, buffer_realloc);

	SoWriteAction wa(&out);
	wa.apply(root);

	SbString s(buffer);
	free(buffer);
	return s;
}


bool subdivide(vector<vector<float> > &coeffs) {
	if (!mutants || !shape || !coords) return false;
	cerr << "SUBDIVIDING\n\n\n\n\n";
	mutants->removeAllChildren();
	SoSeparator *res = new SoSeparator();
	SoMaterial *mat = new SoMaterial;
	mat->diffuseColor.set1Value(0, 0.6, 0.6, 0.0);
	mat->diffuseColor.set1Value(1, 0.0, 0.6, 0.0);
	mat->diffuseColor.set1Value(2, 1.6, 0.6, 1.0);
	mat->diffuseColor.set1Value(3, 1.0, 0.6, 0.0);
	SoMaterialBinding *matbind = new SoMaterialBinding();
	matbind->value = SoMaterialBinding::PER_FACE_INDEXED;
	SoCoordinate3 * c = new SoCoordinate3();
	SoIndexedFaceSet *s = new SoIndexedFaceSet();
	loadShape(shape, coords, he);
	for (int i=0; i<niterations; i++) {
		he.subdivide(coeffs);
	}
//		cerr << he << endl;
	unloadShape(s, c, he);
	res->addChild(c);
	res->addChild(mat);
	res->addChild(matbind);
	res->addChild(s);
	mutants->addChild(res);
	return true;

}

void offrender(SoQtExaminerViewer * viewer) {
	subdivide(coeffs);
	SbViewportRegion vp;
	vp.setWindowSize(640,480);
	SoOffscreenRenderer *myRenderer = new SoOffscreenRenderer(vp);//viewer->getViewportRegion());




	if (!myRenderer->render(viewer->getSceneManager()->getSceneGraph())) {
		delete myRenderer;
		exit(-1);
	}

	SbString framefile;
	stringstream ss;

	ss.setf ( std::ios_base::fixed);
	ss.precision(10);
	float mincurv = he.total_curvature();
	ss << right << "out/";
	ss.fill('0');
	ss.width(50);
	ss << right << mincurv << ".png";
	cerr << ss.str() << endl;
	myRenderer->writeToFile(ss.str().c_str(), "png");
	delete myRenderer;
}

static void event_cb(void * ud, SoEventCallback * n) {
	SoQtExaminerViewer * viewer = (SoQtExaminerViewer *)ud;
	if (SO_KEY_PRESS_EVENT(n->getEvent(), C)) { // centers
		float mincurv = -1;
		float minl0   = -1;
		float minl1   = -1;
		float oldl0 = coeffs[7][0];
		float oldl1 = coeffs[7][1];
//		float l0 = oldl0;
		for (float l0=oldl0-.5; l0<oldl0+.5; l0+=.01) {
			for (float l1=oldl1-.5; l1<oldl1+.5; l1+=.01) {
				coeffs[7][0] = l0;
				coeffs[7][1] = l1;
				loadShape(shape, coords, he);
				for (int i=0; i<niterations; i++) {
					he.subdivide(coeffs);
				}
				float curv = he.total_curvature();
				if (mincurv<0 || mincurv>curv) {
					mincurv = curv;
					minl0 = l0;
					minl1 = l1;
					offrender(viewer);
				}
				cerr << "mincurv: " << mincurv << "\t min: (" << minl0 << ", " << minl1 <<  ")\t, act: " << l0 << ", " << l1 << endl;
		}
			}
		coeffs[7][0] = minl0;
		coeffs[7][1] = minl1;
		subdivide(coeffs);
		return;
	}
	if (SO_KEY_PRESS_EVENT(n->getEvent(), T)) { // star
		float mincurv = -1;
		float minl0   = -1;
		float minl1   = -1;
		float oldl0 = coeffs[0][0];
		float oldl1 = coeffs[0][1];
		cerr << "coeffs[0][]: " <<  coeffs[0][0] << " " << coeffs[0][1] << endl;
		for (float l0=oldl0-.2; l0<oldl0+.2; l0+=.01) {
			for (float l1=oldl1-.2; l1<oldl1+.2; l1+=.01) {
				coeffs[0][0] = l0;
				coeffs[0][1] = l1;
				loadShape(shape, coords, he);
				for (int i=0; i<niterations; i++) {
					he.subdivide(coeffs);
				}
				float curv = he.total_curvature();
				if (mincurv<0 || mincurv>curv) {
					mincurv = curv;
					minl0 = l0;
					minl1 = l1;
					offrender(viewer);
				}
				cerr << "mincurv: " << mincurv << "\t min: (" << minl0 << ", " << minl1 <<  ")\t, act: " << l0 << ", " << l1 << endl;
			}
		}
		coeffs[0][0] = minl0;
		coeffs[0][1] = minl1;
		subdivide(coeffs);
		return;
	}
	if (SO_KEY_PRESS_EVENT(n->getEvent(), A)) { // Ace
		float mincurv = -1;
		float minl0   = -1;
		float minl1   = -1;
		float minl2   = -1;
		float oldl0 = coeffs[1][0];
		float oldl1 = coeffs[1][1];
		float oldl2 = coeffs[1][2];
		for (float l0=oldl0-.5; l0<oldl0+.5; l0+=.05) {
			for (float l1=oldl1-.5; l1<oldl1+.5; l1+=.05) {
				for (float l2=oldl2-.5; l2<oldl2+.5; l2+=.05) {
					coeffs[1][0] = l0;
					coeffs[1][1] = l1;
					coeffs[1][2] = l2;
					loadShape(shape, coords, he);
					for (int i=0; i<niterations; i++) {
						he.subdivide(coeffs);
					}
					float curv = he.total_curvature();
					if (mincurv<0 || mincurv>curv) {
						mincurv = curv;
						minl0 = l0;
						minl1 = l1;
						minl2 = l2;
						offrender(viewer);
					}
					cerr << "mincurv: " << mincurv << "\t min: (" << minl0 << ", " << minl1 << ", " << minl2 << ")\t, act: " << curv << ": " << l0 << ", " << l1 << ", " << l2 << endl;
				}
			}
		}
		coeffs[1][0] = minl0;
		coeffs[1][1] = minl1;
		coeffs[1][2] = minl2;
		subdivide(coeffs);
		return;
	}
	if (SO_KEY_PRESS_EVENT(n->getEvent(), D)) { //D
		float mincurv = -1;
		float minl0   = -1;
		float minl1   = -1;
		float minl2   = -1;
		float oldl0 = coeffs[9][0];
		float oldl1 = coeffs[9][1];
		float oldl2 = coeffs[9][2];
		for (float l0=oldl0-.5; l0<oldl0+.5; l0+=.05) {
			for (float l1=oldl1-.5; l1<oldl1+.5; l1+=.05) {
				for (float l2=oldl2-.5; l2<oldl2+.5; l2+=.05) {
					coeffs[9][0] = l0;
					coeffs[9][1] = l1;
					coeffs[9][2] = l2;
					loadShape(shape, coords, he);
					for (int i=0; i<niterations; i++) {
						he.subdivide(coeffs);
					}
					float curv = he.total_curvature();
					if (mincurv<0 || mincurv>curv) {
						mincurv = curv;
						minl0 = l0;
						minl1 = l1;
						minl2 = l2;
						offrender(viewer);
					}
					cerr << "mincurv: " << mincurv << "\t min: (" << minl0 << ", " << minl1 << ", " << minl2 << ")\t, act: " << curv << ": " << l0 << ", " << l1 << ", " << l2 << endl;
				}
			}
		}
		coeffs[9][0] = minl0;
		coeffs[9][1] = minl1;
		coeffs[9][2] = minl2;
		subdivide(coeffs);
		return;
	}
	if (SO_KEY_PRESS_EVENT(n->getEvent(), U)) { // sun
		float mincurv = -1;
		float minl0   = -1;
		float oldl0 = coeffs[2][0];
		for (float l0=oldl0-.2; l0<oldl0+.2; l0+=.01) {
				coeffs[2][0] = l0;
				loadShape(shape, coords, he);
				for (int i=0; i<niterations; i++) {
					he.subdivide(coeffs);
				}
				float curv = he.total_curvature();
				if (mincurv<0 || mincurv>curv) {
					mincurv = curv;
					minl0 = l0;
					offrender(viewer);
				}
				cerr << "mincurv: " << mincurv << "\t min: (" << minl0 <<   ")\t, act: " << l0 <<  endl;
			}
		coeffs[2][0] = minl0;
		subdivide(coeffs);
		return;
	}
	if (SO_KEY_PRESS_EVENT(n->getEvent(), E)) { // sun
		float mincurv = -1;
		float minl0   = -1;
		float oldl0 = coeffs[8][0];
		for (float l0=oldl0-.8; l0<oldl0+.8; l0+=.01) {
				coeffs[8][0] = l0;
				loadShape(shape, coords, he);
				for (int i=0; i<niterations; i++) {
					he.subdivide(coeffs);
				}
				float curv = he.total_curvature();
				if (mincurv<0 || mincurv>curv) {
					mincurv = curv;
					minl0 = l0;
					offrender(viewer);
				}
				cerr << "mincurv: " << mincurv << "\t min: (" << minl0 <<   ")\t, act: " << l0 <<  endl;
			}
		coeffs[8][0] = minl0;
		subdivide(coeffs);
		return;
	}
	if (SO_KEY_PRESS_EVENT(n->getEvent(), K)) { // King
		float mincurv = -1;
		float minl0   = -1;
		float minl1   = -1;
		float minl2   = -1;
		float minl3   = -1;
		float minl4   = -1;
		float oldl0 = coeffs[3][0];
		float oldl1 = coeffs[3][1];
		float oldl2 = coeffs[3][2];
		float oldl3 = coeffs[3][3];
		float oldl4 = coeffs[3][4];
		for (float l0=oldl0-.2; l0<oldl0+.4; l0+=.05) {
			for (float l1=oldl1-.2; l1<oldl1+.4; l1+=.05) {
				for (float l2=oldl2-.2; l2<oldl2+.4; l2+=.05) {
					for (float l3=oldl3-.2; l3<oldl3+.4; l3+=.05) {
						for (float l4=oldl4-.2; l4<oldl4+.4; l4+=.05) {
							coeffs[3][0] = l0;
							coeffs[3][1] = l1;
							coeffs[3][2] = l2;
							coeffs[3][3] = l3;
							coeffs[3][4] = l4;
							loadShape(shape, coords, he);
							for (int i=0; i<niterations; i++) {
								he.subdivide(coeffs);
							}
							float curv = he.total_curvature();
							if (mincurv<0 || mincurv>curv) {
								mincurv = curv;
								minl0 = l0;
								minl1 = l1;
								minl2 = l2;
								minl3 = l3;
								minl4 = l4;
								offrender(viewer);
							}
							cerr << "mincurv: " << mincurv << "\t min: (" << minl0 << ", " << minl1 << ", " << minl2 << ", " << minl3 << ", " << minl4 << ")\t, act: " << curv<< " :"<< l0 << ", " << l1 << ", " << l2 << ", " << l3 << ", " << l4 << endl;
						}
					}
				}
			}
		}
		coeffs[3][0] = minl0;
		coeffs[3][1] = minl1;
		coeffs[3][2] = minl2;
		coeffs[3][3] = minl3;
		coeffs[3][4] = minl4;
		subdivide(coeffs);
		return;

	}
	/*
	if (SO_KEY_PRESS_EVENT(n->getEvent(), D)) { // Deuce
		float mincurv = -1;
		float minl0   = -1;
		float minl1   = -1;
		float minl2   = -1;
		float minl3   = -1;
		float oldl0 = coeffs[6][0];
		float oldl1 = coeffs[6][1];
		float oldl2 = coeffs[6][2];
		float oldl3 = coeffs[6][3];
		for (float l0=oldl0-.2; l0<oldl0+.2; l0+=.05) {
			for (float l1=oldl1-.2; l1<oldl1+.2; l1+=.05) {
				for (float l2=oldl2-.2; l2<oldl2+.2; l2+=.05) {
					for (float l3=oldl3-.2; l3<oldl3+.2; l3+=.05) {
							coeffs[6][0] = l0;
							coeffs[6][1] = l1;
							coeffs[6][2] = l2;
							coeffs[6][3] = l3;
							loadShape(shape, coords, he);
							for (int i=0; i<niterations; i++) {
								he.subdivide(coeffs);
							}
							float curv = he.total_curvature();
							if (mincurv<0 || mincurv>curv) {
								mincurv = curv;
								minl0 = l0;
								minl1 = l1;
								minl2 = l2;
								minl3 = l3;
								offrender(viewer);
							}
							cerr << "mincurv: " << mincurv << "\t min: (" << minl0 << ", " << minl1 << ", " << minl2 << ", " << minl3 << ")\t, act: " << curv<< " :"<< l0 << ", " << l1 << ", " << l2 << ", " << l3 << endl;
						}
					}
			}
		}
		coeffs[6][0] = minl0;
		coeffs[6][1] = minl1;
		coeffs[6][2] = minl2;
		coeffs[6][3] = minl3;
		subdivide(coeffs);
		return;
	}
	*/
	if (SO_KEY_PRESS_EVENT(n->getEvent(), R)) {
		init();
		subdivide(coeffs);
		return;
	}
	if (SO_KEY_PRESS_EVENT(n->getEvent(), PAD_ADD)) {
		niterations++;
		subdivide(coeffs);
		return;
	}
	if (SO_KEY_PRESS_EVENT(n->getEvent(), PAD_SUBTRACT)) {
		niterations--;
		subdivide(coeffs);
		return;
	}
	if (SO_KEY_PRESS_EVENT(n->getEvent(), I)) {
		SbString s = buffer_writeaction(viewer->getSceneGraph());
		stringstream ss;
		ss << "out_a=" << coeffs[1][0] << ",b=" << coeffs[1][1] << ",c=" << coeffs[1][3] << ", f=" << coeffs[0][1] << ",g=" << coeffs[0][2] << ".wrl";
		ofstream outfile;
		outfile.open (ss.str().c_str());
		outfile << "#VRML V1.0 ascii" << endl;
		outfile << s.getString();
		outfile.close();
//		(void)fprintf(stdout, "%s\n", s.getString());
		return;
	}
}

bool extract_pointers(SoSeparator *scene, SoIndexedFaceSet *&shape, SoCoordinate3 *&coords) {
	SoSearchAction sa;
	sa.setType(SoIndexedFaceSet::getClassTypeId());
	sa.setInterest(SoSearchAction::ALL);
	sa.apply(scene);
	SoPathList plist = sa.getPaths();
	int len = plist.getLength();
	cerr << "Searching for IndexedFaceSet node...";
	if (len<=0) {
		cerr << "Could not find SoIndexedFaceSet\n";
		return false;
	}
	cerr << "found.\n";

	SoPath *path = (SoPath*) plist[0];
	shape = (SoIndexedFaceSet *)path->getTail();
	SoGroup *shapeParent = (SoGroup *)path->getNodeFromTail(1);
	cerr << "Searching for Coordinate3 node...";
	sa.setType(SoCoordinate3::getClassTypeId());
	sa.setSearchingAll(FALSE); // don't look under off switches
	sa.setInterest(SoSearchAction::LAST);
	sa.apply(shapeParent);

	path = sa.getPath();
	if (!path) {
		cerr << "Could not find Coordinate3 node!\n";
		return false;
	}
	cerr << "found.\n";
	coords = (SoCoordinate3 *)path->getTail();
	return true;
}

// Callback for the point draggers.
void draggerCB(void *data, SoSensor *sensor) {
	if (!sensor) return;
	SoField *field = ((SoFieldSensor *)sensor)->getAttachedField();
	if (!field) return;
	SbVec3f  trans = ((SoSFVec3f *)field)->getValue();
	trans[0] *= scaleFactor[0]; // Scale down translations
	trans[1] *= scaleFactor[1];
	trans[2] *= scaleFactor[2];
	if (!data) {
		float f = trans[0];
		float g = trans[1];
		if (f>.5) {
//	coeffs[2][0] = f;
//			set_sun(f);
		} else {
//	coeffs[0][0] = f;
//	coeffs[0][1] = g;
//		set_star(f, g);
		}
//		coeffs[3][0] = 1-5*f-5*g;
//		coeffs[3][1] = f;
//		coeffs[3][2] = g;
		cerr << " f:" << f << ", g:" << g << endl;
	} else {
		float a = trans[0]; //l0
		float b = trans[1]; //l2
		float c = trans[2]; //l1
//		coeffs[9][0] = a;
//		coeffs[9][1] = b;
//		coeffs[9][2] = c;
	}

	subdivide(coeffs);
}

int main(int argc, char *argv[]) {
	QWidget *myWindow = SoQt::init(argv[0]);
	if (myWindow == NULL) exit(1);

	SoQtExaminerViewer *myViewer = new SoQtExaminerViewer(myWindow);
	SoInput in;
	SoSeparator *root = NULL;

	if (in.openFile("draggers.iv")) {
		root = SoDB::readAll(&in);
		if (!root) {
			return 1;
		}
	}
	root->ref();

	SoSearchAction sa;
	sa.setType(SoScale::getClassTypeId());
	sa.setInterest(SoSearchAction::LAST);
	sa.apply(root);

	SoPath *path = sa.getPath();
	cerr << "Searching for SoScale node...";
	if (!path) {
		cerr << "Could not find SoScale node!\n";
		return 1;
	}
	cerr << "found.\n";
	SoScale *scale = (SoScale *)path->getTail();
	scaleFactor = scale->scaleFactor.getValue();

	sa.setType(SoDragPointDragger::getClassTypeId());
	sa.apply(root);
	path = sa.getPath();
	cerr << "Searching for SoDragPointDragger node...";
	if (!path) {
		cerr << "Could not find SoDragPointDragger node!\n";
		return 1;
	}
	cerr << "found.\n";
	pd = (SoDragPointDragger *)path->getTail();
	SoFieldSensor *fieldsensor = new SoFieldSensor(draggerCB, pd);
	fieldsensor->attach(&pd->translation);

	sa.setType(SoTranslate2Dragger::getClassTypeId());
	sa.apply(root);
	path = sa.getPath();
	cerr << "Searching for SoTranslate2Dragger node...";
	if (!path) {
		cerr << "Could not find SoTranslate2Dragger node!\n";
		return 1;
	}
	cerr << "found.\n";
	td = (SoTranslate2Dragger *)path->getTail();
	SoFieldSensor *fieldsensor2 = new SoFieldSensor(draggerCB, NULL);
	fieldsensor2->attach(&td->translation);

	sa.setType(SoTranslate1Dragger::getClassTypeId());
	sa.apply(root);
	path = sa.getPath();
	cerr << "Searching for SoTranslate1Dragger node...";
	if (!path) {
		cerr << "Could not find SoTranslate1Dragger node!\n";
		return 1;
	}
	cerr << "found.\n";
	t1d = (SoTranslate1Dragger *)path->getTail();
	SoFieldSensor *fieldsensor3 = new SoFieldSensor(draggerCB, NULL);
	fieldsensor3->attach(&t1d->translation);

	SoEventCallback * ecb_k = new SoEventCallback;
	ecb_k->addEventCallback(SoKeyboardEvent::getClassTypeId(), event_cb, myViewer);
	root->addChild(ecb_k);

	SoEventCallback * ecb_m = new SoEventCallback;
	ecb_m->addEventCallback(SoMouseButtonEvent::getClassTypeId(), event_cb, myViewer);
	root->addChild(ecb_m);

	mutants = new SoGroup;
	root->addChild(mutants);

	SoSeparator *model = NULL;
	if (in.openFile(1<argc?argv[argc-1]:"wrl/sun-aceq-jack-aceq.wrl")) {
		model = SoDB::readAll(&in);
		if (!model) {
			return 1;
		}
	}
	model->ref();

	if (!extract_pointers(model, shape, coords)) {
		cerr << "failed to extract pointers" << endl;
		return 1;
	}
	init();
	subdivide(coeffs);

	myViewer->setSceneGraph(root);
	myViewer->setTitle("Slider Box");
	myViewer->show();
	SoQt::show(myWindow);
	myViewer->viewAll();
	SoQt::mainLoop();
	model->unref();
	return(0);
}
